﻿/*
NTerm: A .NET based terminal abstraction library
Copyright (C) 2011  Bryan Perris

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Threading;

namespace NTerm
{
    /// <summary>
    /// Internal class
    /// </summary>
    internal sealed class CellBuffer
    {
        private int m_BufferWidth;
        private int m_BufferHeight;
        private List<CharacterCell[]> m_CellRows;

        public CellBuffer(int width, int height)
        {
            m_BufferWidth = width;
            m_BufferHeight = height;
            InitBuffer();
        }

        public CellBuffer(int width, int height, CellBuffer oldBuffer)
        {
            if (oldBuffer == null)
                throw new ArgumentNullException("oldBUffer");

            m_BufferWidth = width;
            m_BufferHeight = height;
            InitBuffer();
            
            Rectangle region = new Rectangle(0, 0, width, height);

            if (width > oldBuffer.BufferWidth)
                region.w = oldBuffer.BufferWidth;

            if (height > oldBuffer.BufferHeight)
                region.h = oldBuffer.BufferHeight;

            FillCellRegion(oldBuffer.GetCellRegion(region), region);
        }

        public CharacterCell[,] GetCellRegion(Rectangle rectangle)
        {
            CharacterCell[,] copiedCells = new CharacterCell[rectangle.h, rectangle.w];
            
            for (int row = 0; row < copiedCells.GetLength(0); row++)
            {
                for (int col = 0; col < copiedCells.GetLength(1); col++)
                {
                    copiedCells[row, col] = this[rectangle.x + col, rectangle.y + row];
                }
            }

            return copiedCells;
        }

        public void FillCellRegion(CharacterCell[,] cells, Rectangle region)
        {
            for (int row = 0; row < region.h; row++)
            {
                for (int col = 0; col < region.w; col++)
                {
                    this[region.x + col, region.y + row] = cells[row, col];
                }
            }
        }

        public void FillCellRegion(CharacterCell cell, Rectangle region)
        {
            CharacterCell[,] cells = new CharacterCell[region.h, region.w];

            for (int y = 0; y < cells.GetLength(0); y++)
            {
                for (int x = 0; x < cells.GetLength(1); x++)
                {
                    cells[y, x] = cell;
                }
            }

            FillCellRegion(cells, region);
        }

        /// <summary>
        /// Scrolls the internal buffer
        /// </summary>
        /// <param name="region">The block of cells to move</param>
        /// <param name="clipRegion">The area of the cell buffer that will be affected by the move, null rects are acceptable (Rectangle.GetNull)</param>
        /// <param name="destRow">The row where to move the block to</param>
        /// <param name="destCol">The column where to move the block to</param>
        /// <param name="fillChar">The character to fill in the spot (if inside the clip region too) where the block moved from</param>
        public void ScrollBuffer(Rectangle region, Rectangle clipRegion, int destRow, int destCol, CharacterCell fillChar)
        {
            // Copy the source cells
            CharacterCell[,] copiedCells = GetCellRegion(region);
            
            // Copy the cells to the new area
            FillCellRegion(copiedCells, new Rectangle(destCol, destRow, copiedCells.GetLength(1), copiedCells.GetLength(0)));

            // clear the old source cells within the real clipping region, if the clip region is not null
            if (!clipRegion.IsNull())
            {
                Rectangle realClipRegion = region & clipRegion;

                if (!realClipRegion.IsNull())
                    FillCellRegion(fillChar, realClipRegion);
            }
        }

        public int BufferWidth
        {
            get { return m_BufferWidth; }
        }

        public int BufferHeight
        {
            get { return m_BufferHeight; }
        }

        public CharacterCell this[int col, int row]
        {
            get 
            { 
#if DEBUG
                CheckCellPostionRange(row, col);
#endif
                return (m_CellRows[row])[col];
            }

            set
            {
#if DEBUG
                CheckCellPostionRange(row, col);
#endif
                (m_CellRows[row])[col] = value;
            }
        }

        private void InitBuffer()
        {
            m_CellRows = new List<CharacterCell[]>(m_BufferHeight);

            for (int i = 0; i < m_BufferHeight; i++)
            {
                m_CellRows.Add(new CharacterCell[m_BufferWidth]);
            }
        }

        private void CheckCellPostionRange(int row, int col)
        {
            if (row < 0 || row >= BufferHeight)
                throw new ArgumentOutOfRangeException("row");

            if (col < 0 || col >= BufferWidth)
                throw new ArgumentOutOfRangeException("col");
        }
    }
}
